/*
 * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Affero General Public License as published by the
 * Free Software Foundation; either version 3 of the License, or (at your
 * option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#include "CreatureScript.h"
#include "ScriptedCreature.h"
#include "SpellScriptLoader.h"
#include "black_temple.h"

enum Says
{
    SAY_BROKEN_FREE_0               = 0,
    SAY_BROKEN_FREE_1               = 1,
    SAY_BROKEN_FREE_2               = 2,
    SAY_BROKEN_S1                   = 0,
    SAY_BROKEN_S2                   = 1
};

enum Spells
{
    SPELL_STEALTH                   = 34189,
    SPELL_AKAMA_SOUL_CHANNEL        = 40447,
    SPELL_SHADE_SOUL_CHANNEL        = 40401,
    SPELL_CHAIN_LIGHTNING           = 39945,
    SPELL_DESTRUCTIVE_POISON        = 40874,
    SPELL_SHADE_OF_AKAMA_TRIGGER    = 40955,
    SPELL_AKAMA_SOUL_RETRIEVE       = 40902,

    SPELL_ASHTONGUE_WAVE_B           = 42035,
    SPELL_SUMMON_ASHTONGUE_SORCERER  = 40476,
    SPELL_SUMMON_ASHTONGUE_DEFENDER  = 40474
};

enum Creatures
{
    NPC_ASHTONGUE_CHANNELER         = 23421,
    NPC_CREATURE_GENERATOR_AKAMA    = 23210,
    NPC_ASHTONGUE_SORCERER          = 23215,
    NPC_ASHTONGUE_BROKEN            = 23319
};

enum Misc
{
    SUMMON_GROUP_BROKENS            = 1,

    POINT_START                     = 0,
    POINT_CHANNEL_SOUL              = 1,

    ACTION_AKAMA_DIED               = 1,
    ACTION_START_ENCOUNTER          = 2,
    ACTION_STOP_SPAWNING            = 3,
    ACTION_DESPAWN_ALL              = 4,
    ACTION_CHANNELERS_START_CHANNEL = 5,
    ACTION_KILL_CHANNELERS          = 6,
    ACTION_NO_SORCERERS             = 7,
    ACTION_SHADE_DIED               = 8,

    EVENT_AKAMA_START_ENCOUNTER     = 1,
    EVENT_AKAMA_START_CHANNEL       = 2,
    EVENT_SPELL_CHAIN_LIGHTNING     = 4,
    EVENT_SPELL_DESTRUCTIVE_POISON  = 5,

    EVENT_SHADE_CHECK_DISTANCE      = 10,
    EVENT_SHADE_RESET_ENCOUNTER     = 11,
    EVENT_SHADE_GATHER_NPCS         = 12,

    EVENT_SUMMON_WAVE_B             = 20,
    EVENT_SUMMON_ASHTONGUE_SORCERER = 21,
    EVENT_SUMMON_ASHTONGUE_DEFENDER = 22,

    EVENT_AKAMA_SCENE0              = 29,
    EVENT_AKAMA_SCENE1              = 30,
    EVENT_AKAMA_SCENE2              = 31,
    EVENT_AKAMA_SCENE3              = 32,
    EVENT_AKAMA_SCENE4              = 33,
    EVENT_AKAMA_SCENE5              = 34,
    EVENT_AKAMA_SCENE6              = 35,
    EVENT_AKAMA_SCENE7              = 36
};

struct boss_shade_of_akama : public BossAI
{
    boss_shade_of_akama(Creature* creature) : BossAI(creature, DATA_SHADE_OF_AKAMA), summonsChanneler(me), summonsGenerator(me)
    {
        events2.ScheduleEvent(EVENT_SHADE_GATHER_NPCS, 1000);
    }

    SummonList summonsChanneler;
    SummonList summonsGenerator;
    EventMap events2;

    void ChannelersAction(int32 action)
    {
        for (SummonList::const_iterator i = summonsChanneler.begin(); i != summonsChanneler.end(); ++i)
            if (Creature* summon = ObjectAccessor::GetCreature(*me, *i))
            {
                if (action == ACTION_CHANNELERS_START_CHANNEL)
                {
                    summon->CastSpell(me, SPELL_SHADE_SOUL_CHANNEL, true);
                    summon->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE);
                }
                else if (action == ACTION_START_ENCOUNTER)
                {
                    summon->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE);
                }
                else if (action == ACTION_KILL_CHANNELERS)
                {
                    Unit::Kill(me, summon);
                }
            }
    }

    void Reset() override
    {
        BossAI::Reset();
        me->SetReactState(REACT_PASSIVE);
        me->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE);
        me->SetImmuneToAll(true);
        me->SetWalk(true);
    }

    void EnterEvadeMode(EvadeReason why) override
    {
        BossAI::EnterEvadeMode(why);
        summonsGenerator.DoAction(ACTION_DESPAWN_ALL);
        events2.ScheduleEvent(EVENT_SHADE_RESET_ENCOUNTER, 20000);
        me->SetVisible(false);
        ChannelersAction(ACTION_KILL_CHANNELERS);
    }

    void JustDied(Unit* killer) override
    {
        BossAI::JustDied(killer);
        summonsGenerator.DoAction(ACTION_DESPAWN_ALL);
        summonsChanneler.DespawnAll();
        me->CastSpell(me, SPELL_SHADE_OF_AKAMA_TRIGGER, true);
        if (Creature* akama = ObjectAccessor::GetCreature(*me, instance->GetGuidData(NPC_AKAMA_SHADE)))
        {
            akama->SetHomePosition(*akama);
            akama->AI()->DoAction(ACTION_SHADE_DIED);
        }
    }

    void DoAction(int32 param) override
    {
        if (param == ACTION_START_ENCOUNTER)
        {
            summonsGenerator.DoAction(ACTION_START_ENCOUNTER);
            ChannelersAction(ACTION_START_ENCOUNTER);
            events.ScheduleEvent(EVENT_SHADE_CHECK_DISTANCE, 1000);
        }
        else if (param == ACTION_AKAMA_DIED)
        {
            EnterEvadeMode(EVADE_REASON_OTHER);
        }
    }

    void UpdateAI(uint32 diff) override
    {
        events2.Update(diff);
        switch (events2.ExecuteEvent())
        {
        case EVENT_SHADE_GATHER_NPCS:
        {
            std::list<Creature*> ChannelerList;
            me->GetCreaturesWithEntryInRange(ChannelerList, 100.0f, NPC_ASHTONGUE_CHANNELER);
            for (std::list<Creature*>::const_iterator itr = ChannelerList.begin(); itr != ChannelerList.end(); ++itr)
                summonsChanneler.Summon(*itr);

            std::list<Creature*> SpawnerList;
            me->GetCreaturesWithEntryInRange(SpawnerList, 100.0f, NPC_CREATURE_GENERATOR_AKAMA);
            for (std::list<Creature*>::const_iterator itr = SpawnerList.begin(); itr != SpawnerList.end(); ++itr)
                summonsGenerator.Summon(*itr);

            summonsChanneler.Respawn();
            summonsGenerator.Respawn();
            ChannelersAction(ACTION_CHANNELERS_START_CHANNEL);

            if (Creature* akama = ObjectAccessor::GetCreature(*me, instance->GetGuidData(NPC_AKAMA_SHADE)))
                akama->Respawn(true);
            break;
        }
        case EVENT_SHADE_RESET_ENCOUNTER:
            me->SetVisible(true);
            summonsGenerator.Respawn();
            summonsChanneler.Respawn();
            ChannelersAction(ACTION_CHANNELERS_START_CHANNEL);

            if (Creature* akama = ObjectAccessor::GetCreature(*me, instance->GetGuidData(NPC_AKAMA_SHADE)))
                akama->Respawn(true);
            break;
        }

        if (!UpdateVictim())
            return;

        events.Update(diff);
        if (me->HasUnitState(UNIT_STATE_CASTING))
            return;

        switch (events.ExecuteEvent())
        {
        case EVENT_SHADE_CHECK_DISTANCE:
            if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() != POINT_MOTION_TYPE)
            {
                int32 slow = me->GetMaxNegativeAuraModifier(SPELL_AURA_MOD_DECREASE_SPEED);
                if (slow > -100)
                {
                    me->SetWalk(true);
                    me->GetMotionMaster()->MovePoint(POINT_START, 510.0f, 400.7993f, 112.7837f);
                }
            }
            else
            {
                int32 slow = me->GetMaxNegativeAuraModifier(SPELL_AURA_MOD_DECREASE_SPEED);
                if (slow < -100)
                    me->GetMotionMaster()->Clear();
                else if (slow == 0)
                {
                    summonsGenerator.DoAction(ACTION_NO_SORCERERS);
                    me->SetWalk(false);
                }
            }

            if (me->IsWithinMeleeRange(me->GetVictim()))
            {
                me->SetReactState(REACT_AGGRESSIVE);
                DoResetThreatList();
                me->GetVictim()->InterruptNonMeleeSpells(false);
                me->AddThreat(me->GetVictim(), 1000000.0f);
                me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE);
                me->SetImmuneToAll(false);
                summonsGenerator.DoAction(ACTION_STOP_SPAWNING);
                break;
            }
            events.ScheduleEvent(EVENT_SHADE_CHECK_DISTANCE, 1000);
            break;
        }

        DoMeleeAttackIfReady();
    }

    bool CheckEvadeIfOutOfCombatArea() const override
    {
        return !SelectTargetFromPlayerList(120.0f);
    }
};

struct npc_akama_shade : public ScriptedAI
{
    npc_akama_shade(Creature* creature) : ScriptedAI(creature), summons(me)
    {
        instance = creature->GetInstanceScript();
    }

    InstanceScript* instance;
    EventMap events;
    EventMap events2;
    SummonList summons;

    void Reset() override
    {
        if (instance->GetBossState(DATA_SHADE_OF_AKAMA) == DONE)
        {
            me->RemoveNpcFlag(UNIT_NPC_FLAG_GOSSIP);
            return;
        }

        me->SetNpcFlag(UNIT_NPC_FLAG_GOSSIP);
        me->CastSpell(me, SPELL_STEALTH, true);
        events.Reset();
        events2.Reset();
    }

    void MovementInform(uint32 type, uint32 point) override
    {
        if (type != POINT_MOTION_TYPE || point != POINT_CHANNEL_SOUL)
            return;

        me->SetFacingTo(0.0f);
        events2.ScheduleEvent(EVENT_AKAMA_SCENE1, 1000);
        events2.ScheduleEvent(EVENT_AKAMA_SCENE2, 16500);
        events2.ScheduleEvent(EVENT_AKAMA_SCENE3, 17500);
        events2.ScheduleEvent(EVENT_AKAMA_SCENE4, 27000);
        events2.ScheduleEvent(EVENT_AKAMA_SCENE5, 37000);
        events2.ScheduleEvent(EVENT_AKAMA_SCENE6, 51000);
        events2.ScheduleEvent(EVENT_AKAMA_SCENE7, 56000);
    }

    void DoAction(int32 param) override
    {
        if (param == ACTION_SHADE_DIED)
            events2.ScheduleEvent(EVENT_AKAMA_SCENE0, 1000);
    }

    void JustDied(Unit* /*killer*/) override
    {
        if (Creature* shade = ObjectAccessor::GetCreature(*me, instance->GetGuidData(NPC_SHADE_OF_AKAMA)))
            shade->AI()->DoAction(ACTION_AKAMA_DIED);
    }

    void JustEngagedWith(Unit* /*who*/) override
    {
        events.ScheduleEvent(EVENT_SPELL_CHAIN_LIGHTNING, 2000);
        events.ScheduleEvent(EVENT_SPELL_DESTRUCTIVE_POISON, 5000);
    }

    void JustSummoned(Creature* summon) override
    {
        float dist = frand(30.0f, 32.0f);
        summon->SetWalk(true);
        summon->GetMotionMaster()->MovePoint(POINT_START, summon->GetPositionX() + dist * cos(summon->GetOrientation()), summon->GetPositionY() + dist * std::sin(summon->GetOrientation()), summon->GetPositionZ(), false);
        summons.Summon(summon);
    }

    void UpdateAI(uint32 diff) override
    {
        events2.Update(diff);
        switch (events2.ExecuteEvent())
        {
        case EVENT_AKAMA_START_ENCOUNTER:
            me->RemoveAura(SPELL_STEALTH);
            me->SetWalk(true);
            me->GetMotionMaster()->MovePoint(POINT_START, 517.4877f, 400.7993f, 112.7837f, false);
            events2.ScheduleEvent(EVENT_AKAMA_START_CHANNEL, 11000);
            break;
        case EVENT_AKAMA_START_CHANNEL:
            me->CastSpell(me, SPELL_AKAMA_SOUL_CHANNEL, false);
            if (Creature* shade = ObjectAccessor::GetCreature(*me, instance->GetGuidData(NPC_SHADE_OF_AKAMA)))
            {
                shade->AI()->AttackStart(me);
                shade->GetMotionMaster()->Clear();
                shade->AI()->DoAction(ACTION_START_ENCOUNTER);
            }
            break;
        case EVENT_AKAMA_SCENE0:
            me->SetWalk(true);
            me->GetMotionMaster()->MovePoint(POINT_CHANNEL_SOUL, 467.0f, 400.7993f, 118.537f);
            break;
        case EVENT_AKAMA_SCENE1:
            me->CastSpell(me, SPELL_AKAMA_SOUL_RETRIEVE, true);
            break;
        case EVENT_AKAMA_SCENE2:
            Talk(SAY_BROKEN_FREE_0);
            break;
        case EVENT_AKAMA_SCENE3:
            me->SummonCreatureGroup(SUMMON_GROUP_BROKENS);
            break;
        case EVENT_AKAMA_SCENE4:
            Talk(SAY_BROKEN_FREE_1);
            break;
        case EVENT_AKAMA_SCENE5:
            for (SummonList::const_iterator itr = summons.begin(); itr != summons.end(); ++itr)
                if (Creature* broken = ObjectAccessor::GetCreature(*me, *itr))
                    broken->SetStandState(UNIT_STAND_STATE_KNEEL);
            Talk(SAY_BROKEN_FREE_2);
            break;
        case EVENT_AKAMA_SCENE6:
            if (Creature* broken = summons.GetCreatureWithEntry(NPC_ASHTONGUE_BROKEN))
                broken->AI()->Talk(SAY_BROKEN_S1);
            break;
        case EVENT_AKAMA_SCENE7:
            for (SummonList::const_iterator itr = summons.begin(); itr != summons.end(); ++itr)
                if (Creature* broken = ObjectAccessor::GetCreature(*me, *itr))
                    broken->AI()->Talk(SAY_BROKEN_S2);
            break;
        }

        if (!UpdateVictim())
            return;

        events.Update(diff);
        if (me->HasUnitState(UNIT_STATE_CASTING))
            return;

        switch (events.ExecuteEvent())
        {
        case EVENT_SPELL_CHAIN_LIGHTNING:
            me->CastSpell(me->GetVictim(), SPELL_CHAIN_LIGHTNING, false);
            events.ScheduleEvent(EVENT_SPELL_CHAIN_LIGHTNING, urand(10000, 15000));
            break;
        case EVENT_SPELL_DESTRUCTIVE_POISON:
            me->CastSpell(me, SPELL_DESTRUCTIVE_POISON, false);
            events.ScheduleEvent(EVENT_SPELL_DESTRUCTIVE_POISON, urand(4000, 5000));
            break;
        }

        DoMeleeAttackIfReady();
    }

    void sGossipSelect(Player* player, uint32 /*sender*/, uint32 action) override
    {
        if (action == 0)
        {
            CloseGossipMenuFor(player);
            me->RemoveNpcFlag(UNIT_NPC_FLAG_GOSSIP);
            events2.ScheduleEvent(EVENT_AKAMA_START_ENCOUNTER, 0);
        }
    }
};

struct npc_creature_generator_akama : public NullCreatureAI
{
    npc_creature_generator_akama(Creature* creature) : NullCreatureAI(creature), summons(me)
    {
        instance = creature->GetInstanceScript();
    }

    void Reset() override
    {
        events.Reset();
        summons.DespawnAll();
    }

    void JustSummoned(Creature* summon) override
    {
        summons.Summon(summon);
        if (summon->GetEntry() == NPC_ASHTONGUE_SORCERER)
        {
            std::list<Creature*> channelerList;
            me->GetCreaturesWithEntryInRange(channelerList, 120.0f, NPC_ASHTONGUE_CHANNELER);
            for (std::list<Creature*>::const_iterator itr = channelerList.begin(); itr != channelerList.end(); ++itr)
            {
                if ((*itr)->IsAlive() || (*itr)->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE))
                    continue;

                summon->SetInCombatWithZone();
                summon->SetReactState(REACT_PASSIVE);
                summon->GetMotionMaster()->MovePoint(POINT_START, **itr);
                (*itr)->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE);
                return;
            }
        }

        summon->SetInCombatWithZone();
        if (Unit* akama = ObjectAccessor::GetCreature(*me, instance->GetGuidData(NPC_AKAMA_SHADE)))
        {
            summon->AddThreat(akama, 500.0f);
            summon->AI()->AttackStart(akama);
        }
    }

    void SummonedCreatureDies(Creature* summon, Unit*) override
    {
        summon->DespawnOrUnsummon(10000);
        summons.Despawn(summon);
    }

    void DoAction(int32 param) override
    {
        if (param == ACTION_STOP_SPAWNING || param == ACTION_DESPAWN_ALL)
        {
            events.Reset();
            for (SummonList::const_iterator itr = summons.begin(); itr != summons.end(); ++itr)
            {
                if (Creature* summon = ObjectAccessor::GetCreature(*me, *itr))
                {
                    if (summon->GetEntry() != NPC_ASHTONGUE_SORCERER)
                        continue;
                    summon->InterruptNonMeleeSpells(false);
                    summon->GetMotionMaster()->Clear();
                    summon->SetInCombatWithZone();
                }
            }
        }
        if (param == ACTION_DESPAWN_ALL)
            summons.DespawnAll();
        else if (param == ACTION_NO_SORCERERS)
            events.CancelEvent(EVENT_SUMMON_ASHTONGUE_SORCERER);
        else if (param == ACTION_START_ENCOUNTER)
        {
            events.ScheduleEvent(EVENT_SUMMON_WAVE_B, 5000);
            events.ScheduleEvent(EVENT_SUMMON_ASHTONGUE_DEFENDER, 20000);
            events.ScheduleEvent(EVENT_SUMMON_ASHTONGUE_SORCERER, 35000);
        }
    }

    void UpdateAI(uint32 diff) override
    {
        events.Update(diff);

        switch (events.ExecuteEvent())
        {
        case EVENT_SUMMON_WAVE_B:
            me->CastSpell(me, SPELL_ASHTONGUE_WAVE_B, true);
            events.ScheduleEvent(EVENT_SUMMON_WAVE_B, 45000);
            break;
        case EVENT_SUMMON_ASHTONGUE_SORCERER: // left
            me->CastSpell(me, SPELL_SUMMON_ASHTONGUE_SORCERER, true);
            events.ScheduleEvent(EVENT_SUMMON_ASHTONGUE_SORCERER, 45000);
            break;
        case EVENT_SUMMON_ASHTONGUE_DEFENDER: // right
            me->CastSpell(me, SPELL_SUMMON_ASHTONGUE_DEFENDER, true);
            events.ScheduleEvent(EVENT_SUMMON_ASHTONGUE_DEFENDER, 45000);
            break;
        default:
            break;
        }
    }

private:
    EventMap events;
    SummonList summons;
    InstanceScript* instance;
};

class spell_shade_of_akama_shade_soul_channel : public AuraScript
{
    PrepareAuraScript(spell_shade_of_akama_shade_soul_channel);

    void HandleEffectApply(AuraEffect const*  /*aurEff*/, AuraEffectHandleModes /*mode*/)
    {
        if (Unit* caster = GetCaster())
            caster->SetFacingToObject(GetTarget());
    }

    void HandleEffectRemove(AuraEffect const*  /*aurEff*/, AuraEffectHandleModes /*mode*/)
    {
        if (Aura* aura = GetTarget()->GetAura(GetSpellInfo()->Effects[EFFECT_1].TriggerSpell))
            aura->ModStackAmount(-1);
    }

    void Register() override
    {
        AfterEffectApply += AuraEffectApplyFn(spell_shade_of_akama_shade_soul_channel::HandleEffectApply, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL);
        AfterEffectRemove += AuraEffectRemoveFn(spell_shade_of_akama_shade_soul_channel::HandleEffectRemove, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL);
    }
};

class spell_shade_of_akama_akama_soul_expel : public SpellScript
{
    PrepareSpellScript(spell_shade_of_akama_akama_soul_expel);

    void SetDest(SpellDestination& dest)
    {
        // Adjust effect summon position
        Position const offset = { 0.0f, 0.0f, 25.0f, 0.0f };
        dest.RelocateOffset(offset);
    }

    void Register() override
    {
        OnDestinationTargetSelect += SpellDestinationTargetSelectFn(spell_shade_of_akama_akama_soul_expel::SetDest, EFFECT_0, TARGET_DEST_CASTER_RADIUS);
    }
};

void AddSC_boss_shade_of_akama()
{
    RegisterBlackTempleCreatureAI(boss_shade_of_akama);
    RegisterBlackTempleCreatureAI(npc_akama_shade);
    RegisterBlackTempleCreatureAI(npc_creature_generator_akama);
    RegisterSpellScript(spell_shade_of_akama_shade_soul_channel);
    RegisterSpellScript(spell_shade_of_akama_akama_soul_expel);
}

